import inspect
import time

from re_common.baselibrary.utils.core.mdeprecated import try_except2_async

from apps.crawler_platform.core_abs.downstepabs import BaseCoreABS
from apps.crawler_platform.core_callback.err_callback import all_client_back_callback
from apps.crawler_platform.core_platform.core_g import IsHasEnum
from apps.crawler_platform.core_platform.core_nosql import NoSqlUtil


class CoreConterollerMixin(BaseCoreABS):
    """
    核心控制器,控制整个流程的逻辑
    """

    # 私有方法不建议外部调用
    async def _base_task_step(self):
        """
        最简单的步骤控制器，会按照数据库配置顺序下载，如果中途任何一个下载失败，
        会返回错误。
        翻页逻辑由down()函数兼容,在翻页时失败不会马上return,而是尽可能的下载其他页
        控制器中keys 为分组与编号的组合，但本控制器不处理分组逻辑
        :return:
        """
        # 限制只允许指定函数调用 由于这里面没有save_sal 由easy_task_step保证save_sal的执行
        self.pm.time_use.print_time("start")
        if inspect.stack()[1].function != "_easy_task_step":
            raise Exception("不允许_easy_task_step以外的函数调用_base_task_step")
        self.step_info = self.add_all_process_info(f"获取redis配置")
        # 获取redis配置信息
        bools, err_info = await self.get_redis()
        self.pm.time_use.print_time("get_redis")
        if not bools:
            self.add_process_info(self.step_info + " 该步骤错误")
            return bools, err_info
        self.step_info = self.add_all_process_info(f"获取单条sql")
        bools, err_info = await self.get_sql()
        self.pm.time_use.print_time("get_sql")
        if not bools:
            self.add_process_info(self.step_info + " 该步骤错误")
            return bools, err_info
        # 有可能出现失败无法加1的情况 先加1做个保险,放在这里而不是放在最开始是因为只有for循环开始才能方便的获取每条的配置
        self.step_info = self.add_all_process_info(f"保存sql的失败加1")
        # 这里失败加1 不用接收bool值，里面用try，不成功也不管，后面有保证。如果出错，后面收集并修复
        await self.save_sql(type="set_failcount_first", code="f_last_key")
        self.pm.time_use.print_time("save_sql_first_failed_1")
        self.step_info = self.add_all_process_info(f"设置html model并进入循环")
        # 设置对应的model来接收下载的结果
        self.pm.set_downmodel()
        # 设置允许最后打标
        self.statemanager.set_pm_sql_and_redis(self.pm, is_state=True)
        self.pm.time_use.print_time("for_start")
        # 对于一个网页可能有多个url需要下载，循环下载
        for keys in sorted(self.get_redis_model().parse_dict.keys()):
            # 设置pm的keys
            self.pm.keys = keys
            # 每个key默认失败
            self.pm.set_is_his(False)

            self.step_info = self.add_all_process_info(f"{keys}:解析redis到单个url")
            bools, err_info = self.para_redis(keys)
            self.pm.time_use.print_time(keys + "_para_redis")
            if not bools:
                self.add_process_info(self.step_info + " 该步骤错误")
                return bools, err_info
            # 有可能出现失败无法加1的情况 先加1做个保险,放在这里而不是放在最开始是因为只有for循环开始才能方便的获取每条的配置
            self.step_info = self.add_all_process_info(f"{keys}:如果没有失败加1，就失败加1")
            # 后面需要改造这个位置，获取到sql就应该失败加一
            await self.save_sql(type="set_failcount", code="f_key;f_last_key")
            self.pm.time_use.print_time(keys + "_for_save_sql")
            if not bools:
                self.add_process_info(self.step_info + " 该步骤错误")
                return bools, err_info
            # 下载重复3次,每次获取新的代理防止代理失效
            self.step_info = self.add_all_process_info(f"{keys}:进入下载")
            bools, err_info = await self.down()
            self.pm.time_use.print_time(keys + "_down")
            if not bools:
                self.add_process_info(self.step_info + " 该步骤错误")
                return bools, err_info
            self.step_info = self.add_all_process_info(f"{keys}:验证")
            bools, err_info = self.verify_html()
            if not bools:
                self.add_process_info(self.step_info + " 该步骤错误")
                return bools, err_info
        self.pm.time_use.print_time("end_for")
        self.step_info = self.add_all_process_info(f"保存nosql")
        # 存入nosql
        bools, err_info = await self.save_nosql_html()
        self.pm.time_use.print_time("save_nosql_html")
        if not bools:
            self.add_process_info(self.step_info + " 该步骤错误")
            return bools, err_info
        self.step_info = self.add_all_process_info(f"解析数据")
        # 解析数据 包括解析上一步需要的sql信息和下一步的信息
        bools, err_info = await self.deal_html()
        self.pm.time_use.print_time("deal_html")
        if not bools:
            self.add_process_info(self.step_info + " 该步骤错误")
            return bools, err_info
        self.step_info = self.add_all_process_info(f"验证解析")
        bools, err_info = self.verify_para()
        if not bools:
            self.add_process_info(self.step_info + " 该步骤错误")
            return bools, err_info
        # 返回一个整体的状态
        return bools, ""

    @try_except2_async(callback=all_client_back_callback, is_print=True)
    async def _easy_task_step(self):
        """
        将save_sql 从base_task_step() 方法独立到easy_task_step中，目的是当base_task_step() 任意地方出现
        错误，也能保证save_sql打标逻辑的正常运行。
        base_task_step 只能被easy_task_step调用
        :return:
        """
        bools, err_info = await self._base_task_step()
        if bools:
            self.statemanager.set_latest_state(True)
        self.step_info = self.add_all_process_info(f"完成开始 save sql")
        # 保存下一级数据和状态打标
        bools1, err_info1 = await self.save_sql()
        self.pm.time_use.print_time("final_save_sql")
        if not bools1:
            self.statemanager.set_latest_state(False)
            self.step_info = self.add_all_process_info(f"save sql失败，save_state保证打标")
            self.add_process_info(self.step_info)
            # 这里的code为msg 后期改正
            bools2, err_info2 = await self.save_sql(type="save_state", code=err_info1)
            self.pm.time_use.print_time("final_save_sql_save_state")
            if not bools2:
                return bools2, err_info2
            return bools1, err_info1

        if bools:
            self.step_info = self.add_all_process_info(f"循环下载完成，所有逻辑成功;")
        return bools, err_info

    @try_except2_async(callback=all_client_back_callback, is_print=True)
    async def _page_task_step(self):
        """
        逻辑上来说翻页下载会有一些失败，但理论上下载完成一轮不管下载了多少都应该
        放入latest。但由于没下载完，为了能跳过应该copy一份放入new_data
        :return:
        """
        bools, err_info = await self._easy_task_step()
        # 设置nosql的newdata字段，防止下载全部成功,但解析不成功的情况
        # 一般不翻页不会做这个设置，直接重新全部下载保持逻辑简单
        if not bools and self.pm.get_is_his(IsHasEnum.LATEST):
            bools_reset, dicts = await NoSqlUtil.reset_new_data(self.pm)
            if not bools_reset:
                self.add_process_info(self.step_info)
                bools, err_info = bools_reset, dicts
        return bools, err_info

    @try_except2_async(callback=all_client_back_callback, is_print=True)
    async def task_step(self):
        """
        最终调度
        """
        if self.pm.turn_page in [1, 2, 5, 6,9,10]:
            return await self._page_task_step()
        elif self.pm.turn_page in [0, 3, 4, 7, 8]:
            return await self._easy_task_step()
        else:
            raise Exception("请配置CoreConterollerMixin task_step对应的turn_page")
